/*
 * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
 * http://www.gnu.org/licenses/lgpl-3.0.html
 */

#ifndef X_CONFIGMANAGER_H
#define X_CONFIGMANAGER_H

#include <wx/hashmap.h>
#include <wx/hashset.h>

#include "settings.h"
#include "globals.h"

#include <tinyxml.h>
#include "manager.h"
#include "base64.h"

#undef new
#include <map>
#include <set>


/* ------------------------------------------------------------------------------------------------------------------
*   接口可序列化
*   配置管理器[ConfigManager]可以保存任意对象,甚至对象的集合/映射,只要提供它们的序列化实现(代码).
*   使用方法如下:
*  ------
*   class MySerializableLongIntClass : public ISerializable
*   {
*   //...
*   wxString SerializeOut() const {wxString tmp; tmp << m_int; return tmp;};
*   void SerializeIn(const wxString& s){s.ToLong(&m_int);};
*   //...
*   long int m_int;
*   };
*/
class ISerializable
{
public:
    ISerializable();
    virtual ~ISerializable();
    virtual wxString SerializeOut() const = 0;
    virtual void SerializeIn(const wxString& s) = 0;
};


/* ------------------------------------------------------------------------------------------------------------------
*  配置管理器[ConfigManager]支持的容器
*/
namespace ConfigManagerContainer
{
    typedef std::map<wxString, wxString> StringToStringMap;
    typedef std::map<int, wxString> IntToStringMap;
    typedef std::set<wxString> StringSet;

    typedef std::map<wxString, ISerializable*> SerializableObjectMap;
}


/* ------------------------------------------------------------------------------------------------------------------*/
/** 搜索目录的值. 在ConfigManager::LocateDataFile()和友元中用作掩码位.*/
enum SearchDirs
{
    sdHome            = 0x0001, ///< User's home directory 用户的主目录
    sdBase            = 0x0002, ///< Code::Blocks' installation base cb的基本安装
    sdTemp            = 0x0004, ///< System-wide temp folder 系统的临时目录
    sdPath            = 0x0008, ///< All dirs in the PATH environment variable 在路径[PATH]环境变量中的所有路径
    sdConfig          = 0x0010, ///< Config folder 配置路径
    sdCurrent         = 0x0020, ///< Current working folder 当前工作路径

    sdPluginsUser     = 0x0100, ///< Plugins folder in user's dir 在用户的目录下的插件路径
    sdScriptsUser     = 0x0200, ///< Scripts folder in user's dir 在用户目录下的脚本路径
    sdDataUser        = 0x0400, ///< Data folder in user's dir 在用户目录下的数据路径

    sdAllUser         = 0x0f00, ///< Convenience value meaning "all sd*User values" 方便值表示"所有sd*用户值"

    sdPluginsGlobal   = 0x1000, ///< Plugins folder in base dir 在基础目录下的插件路径
    sdScriptsGlobal   = 0x2000, ///< Scripts folder in base dir 在基础目录下的脚本路径
    sdDataGlobal      = 0x4000, ///< Data folder in base dir 在基础目录下的数据路径

    sdAllGlobal       = 0xf000, ///< Convenience value meaning "all sd*Global values" 方便值表示"所有sd*全局值"

    sdAllKnown        = 0xffff  ///< All known dirs (i.e. all of the above) 所有可以取得到的目录(即上面所有的目录)
};

/*继承于wxApp实现于App.cpp*/
class CodeBlocksApp;

/* ------------------------------------------------------------------------------------------------------------------
*  ConfigManager class
 *  配置管理器类
*/
class DLLIMPORT ConfigManager
{
    friend class CfgMgrBldr;
    friend class CodeBlocksApp;

    TiXmlDocument *doc;
    TiXmlElement* root;
    TiXmlElement* pathNode;

    ConfigManager(TiXmlElement* r);
    TiXmlElement* AssertPath(wxString& path);
    TiXmlElement* GetUniqElement(TiXmlElement* p, const wxString& q);
    void SetNodeText(TiXmlElement *n, const TiXmlText& t);
    inline void Collapse(wxString& str) const;
    wxString InvalidNameMessage(const wxString& what, const wxString& sub, TiXmlElement *localPath) const;
    static void InitPaths();
    static void MigrateFolders();

    static wxString config_folder;
    static wxString home_folder;
    static wxString data_path_user;
    static wxString data_path_global;

#ifdef CB_AUTOCONF
    static wxString plugin_path_global;
#endif
    static wxString app_path;
    static wxString temp_folder;
    static wxString alternate_user_data_path;
    static bool has_alternate_user_data_path;

protected:

    /*当--user-data-dir开关被设置时，所有的用户配置和用户插件数据都将被设置为相对于这个路径,供CodeBlocksApp使用*/

    static bool SetUserDataFolder(const wxString &user_data_path);

    /*由CfgMgrBldr内部由ConfigManager使用*/
    static wxString GetUserDataFolder();

public:

/*以系统范围内一致的方式访问文件/文件夹的实用程序函数*/
    /**
     * 主要功能:以独立于安装和平台的方式定位文件.
     * 如果您正在寻找"属于Code::Blocks的任意文件",则应该始终使用此函数,
     * 因为它可以跨平台工作,而无需您做任何额外的工作,而且它具有一些(减少)内置冗余.
     * 所以,以前的代码是这样的:
     * wxString some_file = ConfigManager::GetScriptsFolder() + wxFILE_SEP_PATH + _T("startup.script");
     * 应转换为：
     * wxString some_file = ConfigManager::LocateDataFile(_T("startup.script"), sdScriptsUser | sdScriptsGlobal);
     * 这将尝试在全局和用户的脚本文件夹中找到名为"startup.script"的文件.
     * 注:用户的目录 @b 始终优先于全局目录.
     * 参数:文件名,要搜索的文件的名称
     * 参数:search_dir 指定要包含在搜索中的文件夹的一个位掩码.
     */
    static wxString LocateDataFile(const wxString& filename, int search_dirs = sdAllKnown);

    /** 实现功能: 访问Code::Blocks的文件夹之一.
      * 参数说明: dir 要返回的目录.
      * 访问Code：：Blocks的文件夹之一.
      * 指示要返回的目录.
      */
    static wxString GetFolder(SearchDirs dir);

    /**
     ×向后兼容函数.对于新代码,请改用GetFolder()函数替代
     * 查询跨平台工作的"标准"路径
     * 永远不要硬编码"C:\CodeBlocks\share\data"这样的路径
     * 始终使用以下函数之一组成路径.
    */
    static wxString GetHomeFolder() { return GetFolder(sdHome); }
    static wxString GetConfigFolder(){ return GetFolder(sdConfig); }
    static wxString GetPluginsFolder(bool global = true){ return GetFolder(global ? sdPluginsGlobal : sdPluginsUser); }
    static wxString GetScriptsFolder(bool global = true){ return GetFolder(global ? sdScriptsGlobal : sdScriptsUser); }
    static wxString GetDataFolder(bool global = true){ return GetFolder(global ? sdDataGlobal : sdDataUser); }
    static wxString GetExecutableFolder(){ return GetFolder(sdBase); }
    static wxString GetTempFolder(){ return GetFolder(sdTemp); }

    /*用于HTTP/FTP传输的网络代理*/
    static wxString GetProxy();

    /*内置修订信息*/
    static wxString GetRevisionString();
    static unsigned int GetRevisionNumber();
    static wxString GetSvnDate();

    /*用cfg->Read("data_path");代替*/
    static inline wxString ReadDataPath(){return GetDataFolder();}
    /*用cfg->Read("app_path");代替*/
    static inline wxString ReadAppPath(){return GetExecutableFolder();}


    /*用于在配置命名空间中导航的路径函数*/
    wxString GetPath() const;
    void SetPath(const wxString& strPath);
    wxArrayString EnumerateSubPaths(const wxString& path);
    wxArrayString EnumerateKeys(const wxString& path);
    void DeleteSubPath(const wxString& strPath);

    /*
     * 清除命名空间中的所有节点或删除命名空间(从配置文件中删除).
     * 警告:Delete()返回后,指向实例的指针无效.在你调用任何成员之前.
     * 函数中,必须再次调用Manager::Get()->GetConfigManager()以获取有效引用.
     * 注意Delete()本质上是不安全的线程.您将删除整个数据命名空间以及负责处理该数据的对象!确保你知道你在做什么.
     * 这对于DeleteAll()来说更为正确,因为您真的不应该使用它.
     *
    */
    void Clear();
    void Delete();
    void DeleteAll();
    void Flush();

    /*标准原语*/
    void Write(const wxString& name,  const wxString& value, bool ignoreEmpty = false);
    wxString Read(const wxString& key, const wxString& defaultVal = wxEmptyString);
    bool Read(const wxString& key, wxString* str);
    void Write(const wxString& key, const char* str);

    void Write(const wxString& name,  int value);
    bool Read(const wxString& name,  int* value);
    int  ReadInt(const wxString& name,  int defaultVal = 0);

    void Write(const wxString& name,  bool value);
    bool Read(const wxString& name,  bool* value);
    bool ReadBool(const wxString& name,  bool defaultVal = false);

    void Write(const wxString& name,  double value);
    bool Read(const wxString& name,  double* value);
    double ReadDouble(const wxString& name,  double defaultVal = 0.0f);

    /*设置和取消设置键,或者测试是否存在.请注意,这些函数不能用于删除路径或测试路径的存在(可以用于隐式创建路径).*/
    bool Exists(const wxString& name);
    void Set(const wxString& name);
    void UnSet(const wxString& name);

    /*复合对象*/
    void Write(const wxString& name,  const wxArrayString& as);
    void Read(const wxString& name,  wxArrayString* as);
    wxArrayString ReadArrayString(const wxString& name);

    void WriteBinary(const wxString& name,  const wxString& source);
    void WriteBinary(const wxString& name,  void* ptr, size_t len);
    wxString ReadBinary(const wxString& name);

    void Write(const wxString& name,  const wxColour& c);
    bool Read(const wxString& name, wxColour* value);
    wxColour ReadColour(const wxString& name, const wxColour& defaultVal = *wxBLACK);

    /*单个可序列化对象*/
    void Write(const wxString& name, const ISerializable& object);
    bool Read(const wxString& name, ISerializable* object);

    /*基本类型的映射和集合*/
    void Write(const wxString& name, const ConfigManagerContainer::StringToStringMap& map);
    void Read(const wxString& name, ConfigManagerContainer::StringToStringMap* map);
    ConfigManagerContainer::StringToStringMap ReadSSMap(const wxString& name);

    void Write(const wxString& name, const ConfigManagerContainer::IntToStringMap& map);
    void Read(const wxString& name, ConfigManagerContainer::IntToStringMap* map);
    ConfigManagerContainer::IntToStringMap ReadISMap(const wxString& name);

    void Write(const wxString& name, const ConfigManagerContainer::StringSet& set);
    void Read(const wxString& name, ConfigManagerContainer::StringSet* map);
    ConfigManagerContainer::StringSet ReadSSet(const wxString& name);


    /* -----------------------------------------------------------------------------------------------------
    *  序列化对象的映射.您负责删除映射/集合中的对象.
    *
    *  用法:
    *  ------
    *  typedef std::map<wxString, MySerializableClass *> MyMap;
    *  MyMap objMap;
    *  cfg->Read("name", &objMap);
    *  map["somekey"]->DoSomething();
    */

    void Write(const wxString& name, const ConfigManagerContainer::SerializableObjectMap* map);

    template <typename T> void Read(const wxString& name, std::map<wxString, T*> *map)
    {
        wxString key(name);
        TiXmlHandle ph(AssertPath(key));
        TiXmlElement* e = 0;
        if(TiXmlNode *n = ph.FirstChild(key.mb_str(wxConvUTF8)).FirstChild("objmap").Node())
            while(n->IterateChildren(e) && (e = n->IterateChildren(e)->ToElement()))
            {
                T *obj = new T;
                obj->SerializeIn(wxBase64::Decode(cbC2U(e->FirstChild()->ToText()->Value())));
                (*map)[cbC2U(e->Value())] = obj;
            }
    }
};

/*用于读取或写入配置值的包装器类,不需要完整路径.它提供了一种从命名空间详细信息或用于访问配置值的完整路径对代码的一部分进行沙箱处理的方法.*/
class DLLIMPORT ConfigManagerWrapper
{
public:
    ConfigManagerWrapper() {}
    ConfigManagerWrapper(wxString namespace_, wxString basepath) : m_namespace(namespace_), m_basepath(basepath)
    {
        if (!m_basepath.EndsWith(wxT("/"))) { m_basepath += wxT("/"); }
    }
    bool IsValid() const { return !m_namespace.empty(); }
    const wxString& GetBasepath() const { return m_basepath; }

    void Write(const wxString& name, const wxString& value, bool ignoreEmpty = false);
    wxString Read(const wxString& key, const wxString& defaultVal = wxEmptyString);

    bool Read(const wxString& key, wxString* str);
    void Write(const wxString& key, const char* str);

    void Write(const wxString& name,  int value);
    bool Read(const wxString& name,  int* value);
    int  ReadInt(const wxString& name,  int defaultVal = 0);

    void Write(const wxString& name,  bool value);
    bool Read(const wxString& name,  bool* value);
    bool ReadBool(const wxString& name,  bool defaultVal = false);

    void Write(const wxString& name, double value);
    bool Read(const wxString& name, double* value);
    double ReadDouble(const wxString& name, double defaultVal = 0.0f);

private:
    wxString m_namespace;
    wxString m_basepath;
};

/**
 * 阅读批处理生成插件的列表并返回它们.
 * 列表不会为空,至少编译器插件将添加到列表中.
 * 该列表包含插件的库名称,并且在每个操作系统上都不同.
 * */

DLLIMPORT wxArrayString cbReadBatchBuildPlugins();

/*
* 在配置中写入批生成插件的列表.
* @参数:bbpluigns 库插件名称列表.
* @参数:messageBoxParent 设置为出现错误时显示的消息框的父级.
*/

DLLIMPORT void cbWriteBatchBuildPlugins(wxArrayString bbplugins, wxWindow *messageBoxParent);

/* ------------------------------------------------------------------------------------------------------------------
* 配置管理器[ConfigManager]的"生成器模式"类
*  ################################
*  ###### 别去考虑直接使用这个类! ####
*  ################################
*  请使用 Manager::Get()->GetConfigManager("你的命名空间") 代替.
* Manager::Get()->GetConfigManager("你的命名空间")保证在生成器类及其接口可以随时更改而无需事先通知时始终工作
*/

WX_DECLARE_STRING_HASH_MAP(ConfigManager*, NamespaceMap);

class DLLIMPORT CfgMgrBldr : public Mgr<CfgMgrBldr>
{
    friend class ConfigManager;
    friend class Mgr<CfgMgrBldr>;
    NamespaceMap namespaces;
    TiXmlDocument *doc;
    TiXmlDocument *volatile_doc;
    wxCriticalSection cs;
    bool r;
    wxString cfg;

    void Flush();
    void Close();
    void SwitchTo(const wxString& absFN);
    void SwitchToR(const wxString& absFN);
    ConfigManager* Build(const wxString& name_space);
    wxString FindConfigFile(const wxString& filename);
    wxString DetermineExecutablePath();
protected:
    CfgMgrBldr();
    ~CfgMgrBldr();

public:
    static ConfigManager* GetConfigManager(const wxString& name_space);
};

#endif

